package wenjie.chen.receiver.controller

import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import wenjie.chen.receiver.ApplicationTestAware
import wenjie.chen.receiver.commandHandler.impl.AccountCommandHandler
import wenjie.chen.receiver.commandHandler.impl.AccountCommandHandlerCancel
import wenjie.chen.receiver.commandHandler.impl.AccountCommandHandlerConfirm

import static org.mockito.BDDMockito.given
import static org.mockito.internal.verification.VerificationModeFactory.times
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import static wenjie.chen.receiver.command.fixture.IncreaseAmountCommandFixture.aIncreaseAmountCommand

@WebMvcTest
class AccountControllerTest extends ApplicationTestAware {
    private ObjectMapper mapper = new ObjectMapper()

    @Rule
    public ExpectedException thrown = ExpectedException.none()

    @Autowired
    private MockMvc mockMvc

    @MockBean
    private AccountCommandHandler accountCommandHandler

    @MockBean
    private AccountCommandHandlerConfirm accountCommandHandlerConfirm

    @MockBean
    private AccountCommandHandlerCancel accountCommandHandlerCancel

    @Test
    void "should execute try and confirm phases when everything is fine"() {
        def increaseAmountCommand = aIncreaseAmountCommand().build()
        mockMvc
            .perform(post("/increaseAmount")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(mapper.writeValueAsString(increaseAmountCommand)))
            .andExpect(status().isOk())

        Mockito.verify(accountCommandHandler, times(1)).increaseAmount(increaseAmountCommand)
        Mockito.verify(accountCommandHandlerConfirm, times(1)).increaseAmount(increaseAmountCommand)
        Mockito.verify(accountCommandHandlerCancel, times(0)).increaseAmount(increaseAmountCommand)
    }

    @Test
    void "should not execute confirm and cancel phases when try phase thrown an exception and already rollback"() {
        thrown.expectMessage("IllegalStateException")

        def increaseAmountCommand = aIncreaseAmountCommand().build()
        given(accountCommandHandler.increaseAmount(increaseAmountCommand)).willThrow(new IllegalStateException())
        mockMvc
            .perform(post("/increaseAmount")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(mapper.writeValueAsString(increaseAmountCommand)))
            .andExpect(status().isOk())

        Mockito.verify(accountCommandHandler, times(1)).increaseAmount(increaseAmountCommand)
        Mockito.verify(accountCommandHandlerConfirm, times(0)).increaseAmount(increaseAmountCommand)
        Mockito.verify(accountCommandHandlerCancel, times(0)).increaseAmount(increaseAmountCommand)
    }

    /**
     * todo: how to test below scenarios:
     *  1. try phase thrown an exception, and rollback fail. should involve cancel phase
     *  2. try phase success, but transaction manager / compensable manager ask rollback, should involve cancel phase
     *      reproduce: remitter's try phase thrown an exception after receiver's try phase passed.
     *  ...
     */
}
